#
# The package that has the knowledge about what a Linux Counter user
# looks like.
# At the moment, the framework for the Freeze and Thaw methods.
#


package Counter::User;

use strict;
use vars qw(@ISA $DEBUG $TESTMODE);
use vars qw(@ownedfiles @samekeyfiles);
use vars qw(%states);
@ISA = qw(FileTable::TSRecord);
use Fcntl;
use Carp;

require Counter;
require FileTable::TSRecord;

$DEBUG = 0;
$TESTMODE = 0;

# How other files relate to the users file
@ownedfiles = qw(machines friends email);
@samekeyfiles = qw(persons);

# Legal values for some fields
%states = map {$_ => 1} qw(ok bad frozen blocked);

#------------- BASIC STUFF -----------------------
sub store {
    my $self = shift;
    if ($self->{email}) {
	if (!$self->hasemail($self->{email}, "main")) {
	    $self->addemail($self->{email}, "main");
	}
    } else {
	my @oldmains = $self->emails("main");
	if ($oldmains[0]) {
	    $oldmains[0]->{usage} = "old";
	    $oldmains[0]->store();
	}
    }
    if (!$self->{state}) {
	$self->{state} = "ok";
    }
    $self->SUPER::store(@_);
}

sub delete {
    my $self = shift;
    my $filename;
    my @entries;
    my $entry;
    my $frozen;

    for $filename (@ownedfiles) {
	my $file = Counter::openfile($filename);
	@entries = $file->select("owner = ?", $self->key);
	for $entry (@entries) {
	    $entry->delete();
	}
    }
    for $filename (@samekeyfiles) {
	my $file = Counter::openfile($filename);
	$entry = $file->get($self->key);
	if ($entry) {
	    $entry->delete();
	}
    }
    $self->SUPER::delete();
}    

sub privacy {
    my $self = shift;
    my $result;
    if (!$self->{privacy}) {
	my $person = $self->person("clean");
	if ($person->{"may-publish"} eq "YES"
	    || $person->{"may_publish"} eq "YES") {
	    $result = "name";
	} else {
	    $result = "none"; # note: also when not given...
	}
    } else {
	$result = $self->{privacy};
    }
    $result;
}

sub language {
    my $self = shift;
    my $person = $self->person("clean");
    if (!$person) {
	return "i-default"; # see RFC 2277 for where this came from
    } elsif ($person->{language}) {
	return $person->{language};
    } else {
	my $places = Counter::openfile("places");
	my $place = $places->getbykey("name", $person->{placeid});
	my $levels = 0;
	while ($place && !$place->{language}) {
	    $place = $places->get($place->{within});
	    ++ $levels;
	    if ($levels > 10) {
		warn "Probable loop in places DB: ",
		   $place->key(), " ", $place->{name}, "\n";
		return "i-default";
	    }
	}
	if ($place && $place->{language}) {
	    return $place->{language};
	} else {
	    return "i-default";
	}
    }
}
	

#------------- STATE CHANGES -----------------------


sub freeze {
    my $self = shift;
    my $filename;
    my @entries;
    my $entry;
    my $frozen;

    if ($self->{state} eq "frozen") {
	die "Error in freeze: ", $self->key, " already frozen\n";
    }
    if ($self->{state} eq "blocked") {
	die "Error in freeze: ", $self->key, " is in BLOCKED state\n";
    }
    my $freezefile = Counter::openfile("frozen");
    for $filename (@ownedfiles) {
	# NOTE - email is not frozen, to preserve login ability on email
	next if ($filename eq "email");
	$DEBUG && warn "checking $filename\n";
	my $file = Counter::openfile($filename);
	@entries = $file->select("owner = ?", $self->key);
	for $entry (@entries) {
	    $DEBUG && warn "freezing $filename ", $entry->key, "\n";
	    $frozen = $freezefile->newnum();
	    $frozen->{owner} = $self->key;
	    $frozen->{ftable} = $filename;
	    $frozen->{fkey} = $entry->key;
	    $frozen->{textform} = $entry->textform();
	    if (!$TESTMODE) {
		$frozen->store();
		$entry->delete();
	    }
	}
    }
    for $filename (@samekeyfiles) {
	$DEBUG && warn "checking $filename\n";
	my $file = Counter::openfile($filename);
	$entry = $file->get($self->key);
	if ($entry) {
	    $DEBUG && warn "freezing $filename ", $entry->key, "\n";
	    $frozen = $freezefile->newnum();
	    $frozen->{owner} = $self->key;
	    $frozen->{ftable} = $filename;
	    $frozen->{fkey} = $entry->key;
	    $frozen->{textform} = $entry->textform();
	    if (!$TESTMODE) {
		$frozen->store();
		$entry->delete();
	    }
	}
    }
    $DEBUG && warn "Setting entry state to frozen\n";
    $self->{state} = "frozen";
    if (!$TESTMODE) {
	eval '$self->store()';
	if ($@) { # eval died
	    if ($@ =~ /^addemail: Address belongs to/) {
		# because of duplicate email address
		delete $$self{email};
		$self->store();
	    } else {
		die $@;
	    }
	}
    }
    $DEBUG && $TESTMODE && warn "Database not altered\n";
}

sub thaw {
    my $self = shift;
    if ($self->{state} ne "frozen") {
	die "Error in thaw: ", $self->key(), " not in frozen state\n";
    }
    my $entry;
    my %seen;

    my $freezefile = Counter::openfile("frozen");
    $DEBUG && warn "Thawing ", $self->key(), "\n";
    my @recs = $freezefile->select("owner = ?", $self->key());
    $DEBUG && warn "Got back ", @recs + 0, " entries\n";
    for $entry (@recs) {
	my $file = Counter::openfile($entry->{ftable});
	my $rec = $file->makeobject($entry->{fkey}, $entry->{textform});
	# dummy check...
	my $testrec = $file->get($entry->{fkey});
	if ($TESTMODE) {
	    if (!$testrec) {
		warn "No record to clash with\n";
	    } elsif ($testrec->textform() ne $entry->{textform}) {
		warn "Error: ", $testrec->key, " is not equal to ",
		$entry->{ftable}, " ", $entry->{fkey}, "\n";
		warn "-----Frozen-----\n", $entry->{textform}, "\n";
		warn "-----Original-----\n", $testrec->textform(), "\n";
	    } elsif ($testrec->textform() ne $rec->textform()) {
		warn "Error: ", $testrec->key, " is not equal to ",
		$entry->{ftable}, " ", $entry->{fkey}, "\n";
		warn "-----Thawed-----\n", $rec->textform(), "\n";
		warn "-----Original-----\n", $testrec->textform(), "\n";
	    }
	} elsif ($testrec) {
	    if ($seen{$$entry{ftable} . " " . $$entry{fkey}}) {
		warn "Duplicate frozen record - no overwrite: ",
		$entry->{ftable}, " ", $entry->{fkey}, "\n";
	    } else {
		warn "Error: Existing record - no overwrite: ",
		$entry->{ftable}, " ", $entry->{fkey}, "\n";
	    }
	} else {
	    $DEBUG && warn "Restoring ", $entry->{ftable},
	    " ", $entry->{fkey}, "\n";
	    $rec->store();
	    # Remove the frozen record
	    $entry->delete();
	}
	$seen{$$entry{ftable} . " " . $$entry{fkey}} = 1;
    }
    $DEBUG && warn "Setting entry state to ok\n";
    $self->{state} = "ok";
    $self->store();
}

sub hasfrozen {
    my $self = shift;
    if ($self->{state} ne "frozen") {
	return 0;
    }
    my $entry;
    my %seen;

    my $freezefile = Counter::openfile("frozen");
    my @recs = $freezefile->select("owner = ?", $self->key());
    return @recs + 0;
}

sub freezetime {
# return the time the record was frozen, based on the history log
# note: reversing the log would likely be more efficient. but this is quick to code.
    my $self = shift;
    if ($self->{state} ne "frozen") {
	return undef;
    }
    my @hist = FileTable::Log::history($self->tablename, $self->key);
    my $ev;
    my $trailstate;
    my $freezetime;
    # have to debug here...
    for $ev (sort {$$a{date} cmp $$b{date}} @hist) {
	if ($$ev{data} =~ /state:(\S+)/) {
	    my $state = $1;
	    if ($state eq "frozen" && $trailstate ne "frozen") {
		$freezetime = $$ev{date};
	    }
	    $trailstate = $state;
	}
    }
    return $freezetime;
}


# ------------- Email address handling -------------------

use vars qw(%spectags);
%spectags = (
	     "main" => "unique",
	     "old" => "takable",
);


sub emails {
    my $self = shift;
    my $usage = shift;
    my $key = $self->key();
    my $mailfile = Counter::openfile("email");
    if (defined($usage)) {
	return $mailfile->select("owner = ? and email.usage = ?",
				 $key, $usage);
    } else {
	return $mailfile->select("owner = ?", $key);
    }
}

sub addemail {
    my $self = shift;
    my ($addr, $usage, $may_publish) = @_;
    croak "addemail: Address cannot be null" if !$addr;
    my $mailfile = Counter::openfile("email");
    # Do special checking on some special classes
    if ($spectags{$usage} eq "unique") {
	my @olds = $self->emails($usage);
	my $scanner;
	for $scanner (@olds) {
	    next if lc($scanner->key) eq lc($addr);
	    $scanner->{usage} = "old";
	    $scanner->store();
	}
    }
    my $rec = $mailfile->get($addr, O_CREAT);
    if ($rec->{owner}) {
	my $owner = $rec->{owner};
	my $key = $self->key();
	if ($owner eq $key) {
	    if ($rec->{usage} ne $usage) {
		$rec->{usage} = $usage;
                $rec->{may_publish} = $may_publish;
		$rec->store();
	    } else {
		# Possibly we should do nothing here????
		croak "addemail: Address already listed for person";
	    }
	} elsif ($spectags{$rec->{usage}} eq "takable") {
	    $rec->{owner} = $self->key();
	    $rec->{usage} = $usage;
            $rec->{may_publish} = $may_publish;
	    $rec->store();
	} else {
	    croak "addemail: Address belongs to $owner not $key";
	}
    } else {
	$rec->{owner} = $self->key();
	$rec->{usage} = $usage;
        $rec->{may_publish} = $may_publish;
	$rec->{state} = "new";
	$rec->store();
    }
}   

sub hasemail {
    my $self = shift;
    my $addr = shift;
    my $usage = shift;
    croak "hasemail: Address cannot be null" if !$addr;
    my $mailfile = Counter::openfile("email");
    my $rec;
    if ($usage) {
	$rec = $mailfile->select("f_key = ? and owner = ? and email.usage = ?",
				$addr, $self->key(), $usage);
    } else {
	$rec = $mailfile->select("f_key = ? and owner = ?",
				$addr, $self->key());
    }
    return $rec;
}

sub removeemail {
    my $self = shift;
    my $addr = shift;
    my $mailfile = Counter::openfile("email");
    
    my $rec = $mailfile->get($addr);
    my $key = $self->key();
    if ($rec->{owner} ne $key) {
	my $owner = $rec->{owner};
	croak "removeemail: Address belongs to $owner not $key";
    } else {
	$rec->delete();
    }
}

# ------------------- Access to subrecords ----------------------
sub person {
    my $self = shift;
    my $opt = shift;
    my $file = Counter::openfile("persons");
    if (defined($opt) && $opt eq "clean") {
	return $file->getclean($self->key());
    } else {
	return $file->get($self->key(), $opt);
    }
}

sub machines {
    my $self = shift;
    my $key = $self->key();
    my $machfile = Counter::openfile("machines");
    return $machfile->select("owner = ?", $key);
}

sub friends {
    my $self = shift;
    my $key = $self->key();
    my $friendfile = Counter::openfile("friends");
    return $friendfile->select("owner = ?", $key);
}

# Return all "owned" records belonging to this entry
sub ownedrecs {
    my $self = shift;
    my $filename;
    my $file;
    my @recs;
    for $filename (@ownedfiles) {
	$file = Counter::openfile($filename);
	push(@recs, $file->select("owner = ?", $self->key));
    }
    @recs;
}

sub getemail {
   my $self = shift;
   my $addr = shift;
   my $usage = shift;
   croak "hasemail: Address cannot be null" if !$addr;
   my $mailfile = Counter::openfile("email");
   my @reclist;
   if ($usage) {
       @reclist = $mailfile->select("f_key = ? and owner = ? and email.usage = ?",
                  $addr, $self->key(), $usage);
   } else {
       @reclist = $mailfile->select("f_key = ? and owner = ?",
                  $addr, $self->key());
   }
   #warn "Reclist is ", @reclist, "\n";
   #warn "Reclist[0] is ", $reclist[0], "\n";
   # just verify...
   die "getemail: Got multiple emails for $addr of " . $self->key()
       if @reclist + 0 > 1;
   return $reclist[0];
}

1;
